package com.permissions.dispatcher.processor.util;

import static com.permissions.dispatcher.processor.PermissionsProcessor.TYPE_UTILS;


import com.permissions.dispatcher.annotation.OnNeverAskAgain;
import com.permissions.dispatcher.processor.ProcessorUnit;
import com.permissions.dispatcher.processor.RuntimePermissionsElement;
import com.permissions.dispatcher.processor.exception.DuplicatedMethodNameException;
import com.permissions.dispatcher.processor.exception.DuplicatedValueException;
import com.permissions.dispatcher.processor.exception.MixPermissionTypeException;
import com.permissions.dispatcher.processor.exception.NoAnnotatedMethodsException;
import com.permissions.dispatcher.processor.exception.NoParametersAllowedException;
import com.permissions.dispatcher.processor.exception.NoThrowsAllowedException;
import com.permissions.dispatcher.processor.exception.PrivateMethodException;
import com.permissions.dispatcher.processor.exception.SpecialPermissionsWithNeverAskAgainException;
import com.permissions.dispatcher.processor.exception.WrongClassException;
import com.permissions.dispatcher.processor.exception.WrongParametersException;
import com.permissions.dispatcher.processor.exception.WrongReturnTypeException;

import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

/**
 * 检查工具类
 */
public class Validators {
    private static final String WRITE_SETTINGS = "WRITE_SETTINGS";
    private static final String SYSTEM_ALERT_WINDOW = "SYSTEM_ALERT_WINDOW";

    /**
     * 发现和生效ProcessorUnit
     * @param units units
     * @param element element
     * @param <K> <K>
     * @return ProcessorUnit
     */
    public static <K> ProcessorUnit<K> findAndValidateProcessorUnit(List<ProcessorUnit<K>> units, Element element) {
        TypeMirror typeMirror = element.asType();
        for (ProcessorUnit<K> ksal : units) {
            if (isSubtypeOf(typeMirror, ksal.getTargetType())) {
                return ksal;
            }
        }
        throw new WrongClassException(typeMirror);
    }

    /**
     * 是否子类
     * @param typeMirror typeMirror
     * @param ofType ofType
     * @return boolean
     */
    public static boolean isSubtypeOf(TypeMirror typeMirror, TypeMirror ofType) {
        return TYPE_UTILS.isSubtype(typeMirror, ofType);
    }

    /**
     * Checks the elements in the provided list annotated with an annotation against duplicate values.
     * <p>
     * Raises an exception if any annotation value is found multiple times.
     * @param annotationClass 注解类
     * @param methods 执行方法集合
     */
    public static void checkDuplicatedValue(
            List<ExecutableElement> methods, Class<? extends Annotation> annotationClass) {
        Set<List<String>> values = new HashSet<>();
        for (ExecutableElement method : methods) {
            Annotation permissionValue = method.getAnnotation(annotationClass);
            List<String> value = Extensions.permissionValue(permissionValue);
            Collections.sort(value);
            if (values.contains(value)) {
                throw new DuplicatedValueException(value, method, annotationClass);
            } else {
                values.add(value);
            }
        }
    }

    /**
     * 检查是否为空
     * @param items items
     * @param rep rep
     * @param annotationClass annotationClass
     */
    public static void checkNotEmpty(
            List<ExecutableElement> items, RuntimePermissionsElement rep, Class<? extends Annotation> annotationClass) {
        if (items.isEmpty()) {
            throw new NoAnnotatedMethodsException(rep, annotationClass);
        }
    }

    /**
     * 检查是否私有方法
     * @param items items
     * @param annotationClass annotationClass
     */
    public static void checkPrivateMethods(List<ExecutableElement> items, Class<? extends Annotation> annotationClass) {
        for (ExecutableElement it : items) {
            if (it.getModifiers().contains(Modifier.PRIVATE)) {
                throw new PrivateMethodException(it, annotationClass);
            }
        }
    }

    /**
     * 检测方法是否被加密
     * @param items items
     */
    public static void checkMethodSignature(List<ExecutableElement> items) {
        for (ExecutableElement it : items) {
            // Allow 'void' return type only
            if (it.getReturnType().getKind() != TypeKind.VOID) {
                throw new WrongReturnTypeException(it);
            }
            // Allow methods without 'throws' declaration only
            if (!it.getThrownTypes().isEmpty()) {
                throw new NoThrowsAllowedException(it);
            }
        }
    }

    /**
     * 检查方法参数是否有问题
     * @param items items
     * @param numParams numParams
     * @param requiredType requiredType
     */
    public static final void checkMethodParameters(
            List<ExecutableElement> items, int numParams, TypeMirror requiredType) {
        for (ExecutableElement it : items) {
            it.getParameters().isEmpty();
            if (numParams == 0 && !it.getParameters().isEmpty()) {
                throw new NoParametersAllowedException(it);
            }
            if (requiredType == null) {
                return;
            }
            if (numParams < it.getParameters().size()) {
                throw new WrongParametersException(it, numParams, requiredType);
            }
            // maximum params size is 1
            for (VariableElement element : it.getParameters()) {
                if (!TYPE_UTILS.isSameType(element.asType(), requiredType)) {
                    throw new WrongParametersException(it, numParams, requiredType);
                }
            }
        }
    }

    /**
     * 检测默认的权限类型
     * @param items items
     * @param annotationClass annotationClass
     */
    public static void checkMixPermissionType(
            List<ExecutableElement> items, Class<? extends Annotation> annotationClass) {
        for (ExecutableElement item : items) {
            Annotation permissionValue = item.getAnnotation(annotationClass);
            List<String> value = Extensions.permissionValue(permissionValue);
            if (value.size() > 1) {
                if (value.contains(WRITE_SETTINGS)) {
                    throw new MixPermissionTypeException(item, WRITE_SETTINGS);
                } else if (value.contains(SYSTEM_ALERT_WINDOW)) {
                    throw new MixPermissionTypeException(item, SYSTEM_ALERT_WINDOW);
                }
            }
        }
    }

    /**
     * 检测是否特殊权限
     * @param items items
     * @param annotationClass annotationClass
     */
    public static void checkSpecialPermissionsWithNeverAskAgain(
            List<ExecutableElement> items, Class<OnNeverAskAgain> annotationClass) {
        for (ExecutableElement item : items) {
            Annotation permissionValue = item.getAnnotation(annotationClass);
            List<String> value = Extensions.permissionValue(permissionValue);
            if (value.contains(WRITE_SETTINGS) || value.contains(SYSTEM_ALERT_WINDOW)) {
                throw new SpecialPermissionsWithNeverAskAgainException();
            }
        }
    }

    /**
     * 检测重复方法名
     * @param items items
     */
    public static final void checkDuplicatedMethodName(List<ExecutableElement> items) {
        if (items == null || items.size() == 0) {
            return;
        }
        for (ExecutableElement it : items) {
            if (it != items.get(0) && it.getSimpleName() == items.get(0).getSimpleName()) {
                throw new DuplicatedMethodNameException(it);
            }
        }
    }
}
